package SMTPProxy;

use strict;
use MIME::Base64;
use Proxy;
use Socket;
use Log;
use vars qw(@ISA $VERSION);
use ExchangeMail;
#use Net::SMTP::TLS;
use Net::SMTP::SSL;
#use Mail::IMAPClient;
use Mail::POP3Client;

$VERSION = "1.0";

@ISA = qw(Proxy);

my $type="";#exchange#imap#pop3

sub start
{
	my $self=shift;
	$type=shift;
	$self->{'mailusername'}=shift;
	$self->{'mailpassword'}=shift;
	$self->{'maildomain'}=shift;
	$self->SUPER::start(@_,\&smtpinfunc,\&smtpoutfunc,\&smtpinitfunc,\&smtptermfunc);
}

sub maillist
{
	my ($user,$pass,$domain)=@_;
	my $res=[];
	if($type eq "exchange")
	{
		$res=ExchangeMail::listMessages($user,$pass,$domain);
	}
	if($type eq "imap")
	{
		my $client=Mail::IMAPClient->new(Server=>'imap.gmail.com',User=>$user,Password=>$pass,Port=>993,Ssl=>1) or die "Cannot connect through IMAPClient: $!";
		$client->Uid(1);

		if($client->IsAuthenticated())
		{
			if($client->select("inbox"))
			{
				print "Reading inbox\n";
				for($client->unseen)
				{
					my $subject=$client->subject($_);
					my @string=split(/\n/,$client->message_string($_));
					my $from=$client->get_header($_,"From");
					push @$res,{'MessageId'=>$_,'Subject'=>$subject,'From'=>$from,'raw'=>\@string};
				}
			}
		}

		$client->logout();
	}
	if($type eq "pop3")
	{
		my $pop=Mail::POP3Client->new(USER=>$user,PASSWORD=>$pass,HOST=>"pop.gmail.com",PORT=>995,USESSL=>'true');

		my $count=$pop->Count();
		if($count < 0)
		{
			print $pop->Message()."\n";
		}elsif($count == 0){
		}else{
			print "Reading $count messages\n";
			for my $i (1..$count)
			{
				print "Reading message $i\n";
				my $from="";
				my $subject="";
				foreach($pop->Head($i))
				{
					if(/^From:\s*(.*)/)
					{
						$from=$1;
					}
					if(/^Subject:\s*(.*)/)
					{
						$subject=$1;
					}
				}
				my @string=$pop->HeadAndBody($i);
				push @$res,{'MessageId'=>$pop->Uidl($i),'Subject'=>$subject,'From'=>$from,'raw'=>\@string};
			}
		}

		$pop->Close();
	}

	return $res;
}

sub maildel
{
	my ($messid,$user,$pass,$domain)=@_;
	if($type eq "exchange")
	{
		return ExchangeMail::deleteMessage($messid,$user,$pass,$domain);
	}
	if($type eq "imap")
	{
		my $client=Mail::IMAPClient->new(Server=>'imap.gmail.com',User=>$user,Password=>$pass,Port=>993,Ssl=>1) or die "Cannot connect through IMAPClient: $!";
		$client->Uid(1);
		$client->delete_message($messid);
	}
	if($type eq "pop3")
	{
		my $pop=Mail::POP3Client->new(USER=>$user,PASSWORD=>$pass,HOST=>"pop.gmail.com",PORT=>995,USESSL=>'true');

		my $count=$pop->Count();
		if($count < 0)
		{
			die $pop->Message();
		}elsif($count == 0){
		}else{
			for my $i (1..$count)
			{
				if($pop->Uidl($i) == $messid)
				{
					$pop->Delete($i);
				}
			}
		}

		$pop->Close();
	}
}

sub mailsend
{
	my ($subject,$data,$to,$user,$pass,$domain)=@_;
	
	if($type eq "exchange")
	{
		return ExchangeMail::sendMessage($subject,$data,$to,$user,$pass,$domain);
	}
	if($type eq "imap" or $type eq "pop3")
	{
=TLS
		my $mailer=Net::SMTP::TLS->new('smtp.gmail.com',Hello=>'smtp.gmail.com',Port=>587,User=>$user,Password=>$pass);
=cut
#=SSL
		my $mailer=Net::SMTP::SSL->new('smtp.gmail.com',Port=>465);
		$mailer->auth($user,$pass);
#=cut
		$mailer->mail($user);
		$mailer->to($to);
		$mailer->data();
		$mailer->datasend("From: $user\n");
		$mailer->datasend("To: $to\n");
		$mailer->datasend("Subject: $subject\n");
		$mailer->datasend("\n");
		$mailer->datasend("\n");
		$mailer->datasend($data);
		$mailer->dataend();
		$mailer->quit();
		return 1;
	}
}

sub smtpinfunc
{
	my $self=shift;
	my $messages=maillist($self->{'mailusername'},$self->{'mailpassword'},$self->{'maildomain'});
	for my $mesg (@$messages)
	{
		my $msg=$mesg->{'raw'};
		my $ok=0;
		my $id=0;
		my $seq=0;
		while(@$msg)
		{
			my $line=shift @$msg;
			if($line=~/\S/)
			{
				if($line=~/Subject:\s*chain req u (\d+) (\d+)/)
				{
					$id=$1;
					$seq=$2;
					$ok=1;
				}
			}else{
				last;
			}
		}
		if($ok)
		{
			my $state;
			for my $sock (keys %{$self->{'list'}})
			{
				my $sp=$self->{'list'}->{$sock};
				if($sp->{'requestId'} == $id)
				{
					$state=$sp;
				}
			}
			my $data=join("",@$msg);
			if($data=~/hi (.*)/)
			{
				mailsend("chain req u ".$id." ".0,"got hi from $1. hi ".$mesg->{'From'}."!",$mesg->{'From'},$self->{'mailusername'},$self->{'mailpassword'},$self->{'maildomain'});
			}
			if($data=~/connect (\w+):(\d+)/)
			{
				my ($t,$tp)=($1,$2);
				my $client={'proxy'=>$self,'requestId'=>$id,'address'=>$mesg->{'From'},'requestSeq'=>0,'responseSeq'=>0};
				if($t ne "0" && $tp > 0)
				{
					$client->{'client'}=IO::Socket::INET->new(
									PeerAddr => $t,
									PeerPort => $tp,
									Proto    => 'tcp',
								);
					if($client->{'client'})
					{
						$self->{'select'}->add($client->{'client'});
						$self->{'list'}->{$client->{'client'}}=$client;
						Log::_debug("Remote connect detected... $t:$tp opened");
					}else{
						mailsend("chain req u ".$id." ".0,"disconnect",$client->{'address'},$self->{'mailusername'},$self->{'mailpassword'},$self->{'maildomain'});
						Log::_debug("Remote connect detected... $t:$tp failed");
					}
				}else{
					$self->{'list'}->{"postponed$id"}=$client;
					Log::_debug("Remote connect detected... $t:$tp postponed");
				}
			}
			if(defined $state)
			{
				if($data=~/disconnect/)
				{
					Log::_debug("Session closed from remote side");
					$self->{'termfunc'}->($state);
					$self->{'select'}->remove($state->{'client'});
					delete $self->{'list'}->{$state->{'client'}};
					$state->{'client'}->close();
				}
				if($data=~/request (.*)/)
				{
					my $d=decode_base64($1);
					Log::_debug("Resending data with length ".length($d));
					#HTTP
					if(!defined $state->{'client'})
					{
						if($d=~/^(GET|HEAD|POST|PUT|DELETE|TRACE)\s+.*?\s+HTTP\/\d+\.\d+\s+.*?Host\:\s*(\S+)/is)
						{
							delete $self->{'list'}->{"postponed$id"};
							my $host=$1;
							my $port;
							($host,$port)=split(/\:/,$host);
							$port=$port||80;
							my ($t,$tp)=
							$state->{'client'}=IO::Socket::INET->new(
									PeerAddr => scalar(gethostbyname($host)),
									PeerPort => $port,
									Proto    => 'tcp',
								);
							if(defined $state->{'client'})
							{
								$self->{'select'}->add($state->{'client'});
								$self->{'list'}->{$state->{'client'}}=$state;
							}
						}
					}
					if(!defined $state->{'client'})
					{
						mailsend("chain req u ".$id." ".0,"disconnect",$state->{'address'},$self->{'mailusername'},$self->{'mailpassword'},$self->{'maildomain'});
					}else{
						select($state->{'client'});
						$|=1;
						print $d;
						select(STDOUT);
					}
				}
			}
			maildel($mesg->{'MessageId'},$self->{'mailusername'},$self->{'mailpassword'},$self->{'maildomain'});
		}
	}
}

sub smtpoutfunc
{
	my ($self,$d)=@_;
#	Log::_debug("Sending ".$self->{'requestId'}." ".$self->{'requestSeq'}." $d\n--------------------------------\n");
	$d="request ".encode_base64($d);
	mailsend("chain req u ".$self->{'requestId'}." ".$self->{'requestSeq'},$d,$self->{'address'},$self->{'proxy'}->{'mailusername'},$self->{'proxy'}->{'mailpassword'},$self->{'proxy'}->{'maildomain'});
#	Log::_debug("Sent\n--------------------------------\n");
	$self->{'requestSeq'}++;
}

sub smtpinitfunc
{
	my $self=shift;
	my @a=(0..9);
	my $id="";
	for(0..10)
	{
		$id.=$a[int(rand(@a))];
	}
	$self->{'address'}="isd\@acs-it.ru";
	$self->{'requestId'}=$id;
	$self->{'requestSeq'}=0;
	$self->{'responseSeq'}=0;
	Log::_debug("SMTP proxy configured\n");
	mailsend("chain req u ".$self->{'requestId'}." ".$self->{'requestSeq'},"connect ".$self->{'remote_host'}.":".$self->{'remote_port'},$self->{'address'},$self->{'proxy'}->{'mailusername'},$self->{'proxy'}->{'mailpassword'},$self->{'proxy'}->{'maildomain'});
}

sub smtptermfunc
{
	my $self=shift;
	mailsend("chain req u ".$self->{'requestId'}." ".$self->{'requestSeq'},"disconnect",$self->{'address'},$self->{'proxy'}->{'mailusername'},$self->{'proxy'}->{'mailpassword'},$self->{'proxy'}->{'maildomain'});
	my $messages=maillist($self->{'proxy'}->{'mailusername'},$self->{'proxy'}->{'mailpassword'},$self->{'proxy'}->{'maildomain'});
	for my $mesg (@$messages)
	{
		my $msg=$mesg->{'raw'};
		Log::_debug($mesg->{'MessageId'}.": ".@$msg."\n---------------------\n");
		my $ok=0;
		my $id=0;
		my $seq=0;
		while(@$msg)
		{
			my $line=shift @$msg;
			if($line=~/\S/)
			{
				if($line=~/Subject:\s*chain req u (\d+) (\d+)/)
				{
					$id=$1;
					$seq=$2;
					$ok=1;
				}
			}else{
				last;
			}
		}
		if($ok && $id == $self->{'requestId'})
		{
			maildel($mesg->{'MessageId'},$self->{'proxy'}->{'mailusername'},$self->{'proxy'}->{'mailpassword'},$self->{'proxy'}->{'maildomain'});
		}
	}
}

1;